Validation

Validation framework

Validation overview

DataObjects.Net includes consistency validation framework allowing to validate single property values, entire entities and entity graphs.

Each session contains its own ValidationContext – class responsible for validation of all entities changed in this session. Since the context is session-level, validation can be turned on or off independantly for each session by including or excluding SessionOption.ValidateEntitites option. By default the option is included in all session profiles.

Validation context can validate entities:

  • On attempt to change field value. In this case only changing field is validated. If field has any Immediate validators (more on that in Property contraints chapter) they will be triggered one by one. If certain validator returns an error an ArgumentException with error message from validator will be thrown.
  • On transaction commit. When an entity is changed it register itself in validation context for future validation. All registered entities are validated when transaction is about to commit. When it happens all validators are triggered (except for those which are immediate) and if validation is failed validation infrastructure will throw an ValidationFailedException with list of validation errors grouped by entities.

Following example shows where to expect exceptions in both cases

try {
using (var transactionScope = session.OpenTransaction()) {

var user = session.Query.All<User>().First(u => u.Id == currentUserId); try {

// both names have immediate validator for value not being null or empty user.FirstName = updatedFirstName; user.LastName = updatedLastName;

} catch(ArgumentException argException){

Console.WriteLine(“Following validation error was found on attempt to update entity:”); Console.WriteLine(argException.Message); throw;

} // email validator is not immediate user.Email = updateEmail; user.LastUpdateOn = DateTime.UtcNow;

transactionScope.Complete();

}

} catch(ValidationFailedException exception) {

Console.WriteLine(“Following validation errors were found:”); foreach (var errorInfo in exception.ValidationErrors) {

var entity = errorInfo.Target; Console.WriteLine(“- {entity.ToString()}:”); foreach (var validationResult in errorInfo.Errors) {

var errorMessage = validationResult.ErrorMessage; var fieldName = validationResult.Field.Name; Console.WriteLine($” Field {fieldName} caused ‘{errorMessage}’”);

} Console.WriteLine();

}

}

You can aslo perfrom validation manually. Entity.Validate() will validate separate entity, if validation failed it will throw ValidationFailedException. Session.Validate() will validate all entities registered in the validation context at once. Sometimes you also might want to have errors in form of collection instead of exception, if so the Session.ValidateAndGetErrors() method will be useful, it will give you error information like it presented in ValidationFailedException.ValidationErrors.

Object level validation rules

To define validation rules you should implement object-level validation logic in OnValidate() method that will be called on entity validation. This method should check entity state and throw an exception if it is invalid.

[HierarchyRoot]
public class Person : Entity
{
  // ...

  [Field]
  public bool IsSubscribedOnNews { get; set;}

  [Field]
  public string Email { get; set;}

  protected override void OnValidate()
  {
    base.OnValidate();

    if (IsSubscribedOnNews && string.IsNullOrEmpty(Email))
      throw new Exception("Can't subscribe on news (email is not specified).");
  }
}

Property constraints

Overview

Another way to define validation rules is to mark entity properties by special attributes – property constraints. Property constraints are special property-level validation aspects that can be applied to any property by marking it with appropriate attribute. Each constraint implements some simple validation rule for property value. For example NotNullConstraint ensures that property value is not null, RegexConstraint ensures that string value matches specified regular expression pattern.

[LengthConstraint(Min = 2, Max = 128)]
[NotNullOrEmptyConstraint]
public string FirstName { get; set;}

[PastConstraint]
public DateTime BirthDay { get; set; }

[EmailConstraint]
public string Email { get; set;}

Each property constraint attribute has additional properties: - IsImmediate. Set to true, it tells to validate corresponding

field imediately on attempt to set value. false will postpone until transaction commit
  • ValidateOnlyIfModified. By default, validation happens for all validators of an entity - even if some field has not been modified its value will be validated. This option forces not to validate field by the validator if the field value hasn’t been changed.
  • SkipOnTransactionCommit allows to skip validation of by the validator on transaction commit.
[PastConstraint(
  IsImmediate = true,
  ValidateOnlyIfModified = true)]
public DateTime BirthDay { get; set; }

[RangeConstraint(
  Min = 0.8,
  Max = 2.13,
  IsImmediate = true,
  SkipOnTransactionCommit = true)]
public double Height { get; set;}

Pre-defined validation aspects

DataObjects.Net validation framework includes following predefined constraints:

  • EmailConstraint – Ensures that email address is in correct format
  • FutureConstraint – Ensures that date value is in the future
  • LengthConstraint(Min, Max) – Ensures field length (or item count) fits in specified range
  • NotEmptyConstraint – Ensures that property value is not empty string
  • NotNullConstraint – Ensures property value is not null
  • NotNullOrEmptyConstraint – Ensures that property value is not null or empty string
  • PastConstraint – Ensures that date value is in the past
  • RangeConstraint(Min, Max) – Ensures field value fits in the specified range
  • RegexConstraint(Pattern) – Ensures property value matches specified regular expression

Implementing custom constraints

It is possible to implement your own property constraints inheriting it from abstract class PropertyValidator.

[Serializable]
public class PhoneNumberConstraint : PropertyValidator
{
  private const string PhoneNumberPattern = "^[2-9]\\d{2}-\\d{3}-\\d{4}$";

  private static readonly Regex Validator = new Regex(PhoneNumberPattern);

  public override void Configure(Domain domain, TypeInfo type, FieldInfo field)
  {
    base.Configure(domain, type, field);

    if (field.ValueType!=typeof (string))
      ThrowConfigurationError(string.Format(Strings.FieldShouldBeOfTypeX, typeof (string).FullName));
  }

  public override ValidationResult Validate(Entity target, object fieldValue)
  {
    var value = (string) fieldValue;
    var isValid = string.IsNullOrEmpty(value) || Validator.IsMatch(value);
    return isValid ? Success() : Error("Phone number is incorrect", fieldValue);
  }

  public override IPropertyValidator CreateNew()
  {
    return new PhoneNumberConstraint {
      IsImmediate = IsImmediate,
    };
  }
}

In Configure method we can check whether constraint is applied properly, for instance here we check that it is applied to field of string type.

Validate defines validation algorithm itself, as a result you can

return Success or Error with error message or exception instance and actual value that caused the error. CreateNew is used by infrastructure to clone instance of attribute,

you just need to return new instance of the validator with exact same settings.
[PhoneNumberConstraint]
public string Phone { get; set;}